package cn.openea.perpartner.rule.compile;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.tools.*;
import javax.tools.JavaCompiler.CompilationTask;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static java.util.regex.Pattern.compile;


/**
 * @author Weird077
 * @description 动态编译java源码类
 */
public class DynamicRuleCompiler {
    static Logger logger = LoggerFactory.getLogger(DynamicRuleCompiler.class);
    private static ClassLoader parentClassLoader = DynamicRuleCompiler.class.getClassLoader();
    private static String classpath = null;
    private static String compilePath = "";

    static {
        getCompilePath();
        buildClassPath();
    }

    /**
     * 编译指定java源代码
     *
     * @param javaCode java源代码
     * @return 类的全限定名和编译后的class字节码字节数组的映射
     */
    public static Map<String, byte[]> compileJavaCode(String javaCode) {
        Pattern pattern = compile("public\\s+class\\s+(\\w+)");
        if (javaCode.contains("interface")){
            pattern = compile("public\\s+interface\\s+(\\w+)");
        }
        Matcher matcher = pattern.matcher(javaCode);
        if (matcher.find()) {
            return compileJavaCode(matcher.group(1) + ".java", javaCode);
        }
        return Collections.emptyMap();
    }

    /**
     * 编译java代码
     *
     * @param fullClassName Java类全称
     * @param javaCode      Java源码
     * @param createClass    是否生成类文件true表示是，false表示否
     * @return
     */
    public static Map<String, byte[]> compileJavaCode(String fullClassName, String javaCode, boolean createClass)  {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector();
        StandardJavaFileManager stdManager = compiler.getStandardFileManager(diagnostics,  null,  null);
        List<String> options = new ArrayList();
        options.add("-encoding");
        options.add("UTF-8");
        options.add("-d");
        options.add(compilePath);
        options.add("-classpath");
        options.add(classpath);
        try(DynamicJavaFileManager manager = new DynamicJavaFileManager(stdManager, createClass)){
            String javaName = fullClassName.substring(0,fullClassName.lastIndexOf("."));
            javaName = javaName.substring(javaName.lastIndexOf(".")+1);
            JavaFileObject javaFileObject = DynamicJavaFileManager.makeStringSource(javaName+".java", javaCode);
            CompilationTask task = compiler.getTask(null, manager, diagnostics, options, null, Collections.singletonList(javaFileObject));
            boolean success = task.call();
            if (success) {
                if(createClass){
                    DynamicClassLoader classLoader = new DynamicClassLoader(parentClassLoader);
                    return classLoader.loadClassByName(fullClassName);
                }else {
                    return manager.getClassBytes();
                }
            }else {
                String error = "";
                Diagnostic diagnostic;
                for(Iterator iterator = diagnostics.getDiagnostics().iterator(); iterator.hasNext(); error = error + compilePrint(diagnostic)) {
                    diagnostic = (Diagnostic)iterator.next();
                }
            }
        }catch (IOException e) {
            e.printStackTrace();
        }
        return Collections.emptyMap();
    }

    /**
     * 编译指定java源代码
     *
     * @param javaName javaBean
     * @param javaCode java源代码
     * @return 类的全限定名和编译后的class字节码字节数组的映射
     */
    public static Map<String, byte[]> compileJavaCode(String javaName, String javaCode) {
        //当前编译器
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector();
        //Java标准文件管理器
        StandardJavaFileManager stdManager = compiler.getStandardFileManager(diagnostics, null, null);
        //自定义Java文件管理器
        try (DynamicJavaFileManager manager = new DynamicJavaFileManager(stdManager, false)) {
            //Java文件对象
            JavaFileObject javaFileObject = DynamicJavaFileManager.makeStringSource(javaName, javaCode);
            List<String> options = new ArrayList();
            options.add("-encoding");
            options.add("UTF-8");
            options.add("-d");
            options.add(compilePath);
            options.add("-classpath");
            options.add(classpath);
            //设置编译环境
            CompilationTask task = compiler.getTask(null, manager, diagnostics, null, null, Arrays.asList(javaFileObject));
            //编译成功
            boolean success = task.call();
            if (success) {
                return manager.getClassBytes();
            }else {
                String error = "";
                Diagnostic diagnostic;
                for(Iterator iterator = diagnostics.getDiagnostics().iterator(); iterator.hasNext(); error = error + compilePrint(diagnostic)) {
                    diagnostic = (Diagnostic)iterator.next();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return Collections.emptyMap();
    }

    public static String getCompilePath() {
        if (StringUtils.isNotBlank(compilePath)) {
            return compilePath;
        } else {
            URL url = DynamicRuleCompiler.class.getResource("");
            if (url == null) {
                compilePath = DynamicRuleCompiler.class.getResource("/").getPath();
            } else {
                compilePath = DynamicRuleCompiler.class.getResource("").getPath();
            }
            int spos = compilePath.indexOf("/classes/");
            if (spos != -1) {
                compilePath = compilePath.substring(1, spos + 9);
                compilePath = "/" + compilePath;
            }

            return compilePath;
        }
    }

    private static void buildClassPath() {
        classpath = null;
        StringBuilder sb = new StringBuilder();
        String libPath = compilePath.replace("/classes/", "/lib/");
        sb.append(compilePath).append(File.pathSeparator);
        String configLibJarList = "";
        if (StringUtils.isBlank(configLibJarList)) {
            sb.append(listJarFile(libPath));
        } else {
            sb.append(configLibJarList);
        }
        classpath = sb.toString();
    }

    private static StringBuilder listJarFile(String path) {
        StringBuilder sb = new StringBuilder();
        File file = new File(path);
        if (file.isDirectory()) {
            File[] dirFile = file.listFiles();
            File[] arrayOfFile = dirFile;
            int dirFileLength = dirFile.length;
            for (int i = 0; i < dirFileLength; ++i) {
                File f = arrayOfFile[i];
                if (!f.isDirectory() && f.getName().endsWith(".jar")) {
                    sb.append(f.getAbsolutePath()).append(File.pathSeparator);
                }
            }
        }
        return sb;
    }

    private static String compilePrint(Diagnostic diagnostic) {
        logger.info("Code:{}", diagnostic.getCode());
        logger.info("Kind:{}", diagnostic.getKind());
        logger.info("Position:{}", diagnostic.getPosition());
        logger.info("Start Position:{}", diagnostic.getStartPosition());
        logger.info("End Position:{}", diagnostic.getEndPosition());
        logger.info("Source:{}", diagnostic.getSource());
        logger.info("Message:{}", diagnostic.getMessage(null));
        logger.info("LineNumber:{}", diagnostic.getPosition());
        logger.info("ColumnNumber:{}", diagnostic.getColumnNumber());
        StringBuffer res = new StringBuffer();
        res.append("代码:[").append(diagnostic.getCode()).append("]<br>");
        res.append("类型:[").append(diagnostic.getKind()).append("]<br>");
        res.append("消息:[").append(diagnostic.getMessage((Locale) null)).append("]<br>");
        res.append("<font style='color:red' ><b>错误行:[").append(diagnostic.getLineNumber()).append("]</b></font><br>");
        res.append("错误列:[").append(diagnostic.getColumnNumber()).append("]<br>");
        return res.toString();
    }


    public static String encode(String str) {
        try {
            String code = URLEncoder.encode(str, "utf-8");
            code = code.replace("+", "%20");
            return code;
        } catch (Exception e) {
            logger.error("字符串编码失败({})", str);
            return str;
        }
    }
}



